package com.macrolab.secure;
//
//import com.asn1.chaosinmotion.asn1.BerInputStream;
//import com.asn1.chaosinmotion.asn1.Tag;
//import com.asn1.myextend.constant.OID;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.anssi.ANSSINamedCurves;
import org.bouncycastle.asn1.anssi.ANSSIObjectIdentifiers;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
import java.util.Map;

/**
 * ECC 加解密，ECDSA签名验签
 *
 * @author macroLu 2019-5-9
 */
public class ECCUtils {
    private static final Logger log = LoggerFactory.getLogger(ECCUtils.class);

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String ALGORITHM = "EC";
    private static final String ALGORITHM_SIGN = "ECDSA";
    private static final String PROVIDER = "BC";

    public static final String SHA256withECDSA = "SHA256WITHECDSA";
    public static final String SHA384withECDSA = "SHA384withECDSA";
    public static final String SHA512withECDSA = "SHA512withECDSA";

    /**
     * isCompact true:压缩格式  去除签名中的 tag，len ，optional 字节
     */
    public static Boolean isCompact = false;


    public static String getECCurveOIDByName(String ecName) {
        ASN1ObjectIdentifier oid = null;
        switch (ecName) {
            case "P-256":
                oid = SECObjectIdentifiers.secp256r1;
                break;
            case "brainpoolP256r1":
                oid = TeleTrusTObjectIdentifiers.brainpoolP256r1;
                break;
            case "FRP256v1":
                oid = ANSSIObjectIdentifiers.FRP256v1;
                break;
        }

        String result = oid.getId();
        result = oid.toString();
        return result;
    }

    /**
     * 初始化密钥对
     *
     * @param ecName ecc ObjectID 用于对应 标准曲线名称：
     *               brainpoolp256r1 ,
     *               secp256r1(NIST P-256)
     *               FRP256v1
     *               参见：OIDorg.bouncycastle.crypto.ec.CustomNamedCurves
     * @return
     */
    public static KeyPair initKeyPairByECCurveName(String ecName) {
        X9ECParameters curve = null;
        ASN1ObjectIdentifier oid = null;
        switch (ecName) {
            case "P-256":
                curve = NISTNamedCurves.getByName("P-256");
                oid = SECObjectIdentifiers.secp256r1;
                break;
            case "brainpoolP256r1":
                curve = TeleTrusTNamedCurves.getByName("brainpoolP256r1");
                oid = TeleTrusTObjectIdentifiers.brainpoolP256r1;
                break;
            case "FRP256v1":
                curve = ANSSINamedCurves.getByName("FRP256v1");
                oid = ANSSIObjectIdentifiers.FRP256v1;
                break;
        }
        return buildKeyPair(curve, oid);
    }

    /**
     * 初始化密钥对
     *
     * @param eccOID ecc ObjectID 用于对应 标准曲线名称：
     *               brainpoolp256r1 ,
     *               secp256r1(NIST P-256)
     *               FRP256v1
     *               参见：OIDorg.bouncycastle.crypto.ec.CustomNamedCurves
     * @return
     */
    public static KeyPair initKeyPair(String eccOID) {
        X9ECParameters curve = null;
        ASN1ObjectIdentifier oid = null;
//        if (eccOID.equalsIgnoreCase(OID.secp256r1.getCode())) {
//            curve = NISTNamedCurves.getByName("P-256");
//            oid = SECObjectIdentifiers.secp256r1;
//        } else if (eccOID.equalsIgnoreCase(OID.brainpoolP256r1.getCode())) {
//            curve = TeleTrusTNamedCurves.getByName("brainpoolP256r1");
//            oid = TeleTrusTObjectIdentifiers.brainpoolP256r1;
//        } else if (eccOID.equalsIgnoreCase(OID.FRP256v1.getCode())) {
//            curve = ANSSINamedCurves.getByName("FRP256v1");
//            oid = ANSSIObjectIdentifiers.FRP256v1;
//        } else {
//            throw new UnsupportedOperationException("unsupport eccOID: " + eccOID);
//        }
        return buildKeyPair(curve, oid);
    }

    private static KeyPair buildKeyPair(X9ECParameters curve, ASN1ObjectIdentifier oid) {
        X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
        ECCurve ecCurve = ecP.getCurve();
        EllipticCurve ellipticCurve = EC5Util.convertCurve(ecCurve, ecP.getSeed());
        ECParameterSpec ecSpec = new ECNamedCurveSpec(
                ECUtil.getCurveName(oid),
                ellipticCurve,
                new ECPoint(
                        ecP.getG().getAffineXCoord().toBigInteger(),
                        ecP.getG().getAffineYCoord().toBigInteger()),
                ecP.getN(),
                ecP.getH());

        ECDomainParameters domain = new ECDomainParameters(curve.getCurve(), curve.getG(), curve.getN());
        ECKeyPairGenerator generator = new ECKeyPairGenerator();
        ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(domain, new SecureRandom());
        generator.init(keygenParams);
        AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
        ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
        ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
        BCECPublicKey publicKey = new BCECPublicKey(ALGORITHM, pubParams, ecSpec, BouncyCastleProvider.CONFIGURATION);
        BCECPrivateKey privateKey = new BCECPrivateKey(ALGORITHM, privParams, publicKey, ecSpec, BouncyCastleProvider.CONFIGURATION);

        KeyPair keyPair = new KeyPair(publicKey, privateKey);
        return keyPair;
    }

//    /**
//     * 执行签名
//     *
//     * @param privKey   base64格式的私钥
//     * @param plainText 报文
//     * @param algorithm SHA1_SIGN_ALGORITHM | SHA256withECDSA | ....
//     * @return
//     * @throws Exception
//     */
//    public static String sign(String privKey, String plainText, String algorithm) throws Exception {
//        byte[] keyBytes = Base64Utils.decodeFromString(privKey);
//        byte[] data = plainText.getBytes(StandardCharsets.UTF_8.name());
//        return Base64Utils.encodeToString(sign(keyBytes, data, SHA256withECDSA, isCompact));
//    }
//
//    public static String sign(PrivateKey privKey, String plainText) throws Exception {
//        byte[] data = plainText.getBytes(StandardCharsets.UTF_8.name());
//        return Base64Utils.encodeToString(sign(privKey.getEncoded(), data, SHA256withECDSA, isCompact));
//    }
//
//    public static byte[] sign(PrivateKey privKey, byte[] data) throws Exception {
//        return sign(privKey.getEncoded(), data, SHA256withECDSA, isCompact);
//    }

    /**
     * ecc签名格式为 30 + LEN1 + 02 + LEN2 + 00 (optional) + r + 02 + LEN3 + 00(optional) + s
     * 当r或s的第1字节大于0x80时，需要在r或s前加1字节0x00。
     * LEN3为，0x00(optional) + s 的字节长度。
     * LEN2为，0x00(optional) + r 的字节长度。
     * LEN1为，LEN2+LEN3+4字节长度。
     *
     * @param privateKey    签名使用的私钥
     * @param data          签名数据
     * @param signAlgorithm 签名算法：SHA1_SIGN_ALGORITHM, SHA256withECDSA, ....
     * @param isCompact     true:压缩格式  去除签名中的 tag，len ，optional 字节
     * @return 签名结果串
     * @throws Exception
     */
    public static byte[] sign(PrivateKey privateKey, byte[] data, String signAlgorithm, boolean isCompact) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_SIGN, PROVIDER);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        //构建签名
        Signature signature = Signature.getInstance(signAlgorithm);
        signature.initSign(priKey);
        signature.update(data);
        byte[] result = signature.sign();   // 签名后的数据信息

        // ecc签名格式为 30 + LEN1 + 02 + LEN2 + 00 (optional) + r + 02 + LEN3 + 00(optional) + s
//        if (isCompact) {
//            Map isMap = null;
//            try {
//                // log.trace("ecc signature orignal: " + MyTools.toHexStringWithBlankInterval(result));
//                isMap = ASN1Utils.initInputStream("eccSignature", result);
//                byte[] buf, r, s;
//                result = new byte[0x40];
//                BerInputStream bIS = (BerInputStream) isMap.get("berIS");
//                if (bIS.readBerTag() == (Tag.SEQUENCE)) { // tag 30
//                    bIS.readBerLength();
//                    bIS.readBerTag(); // tag 02 ==> R
//                    r = bIS.readOctetString(true);
//                    int startPos = (r.length == 0x21) ? 1 : 0; // 若r长度为 0x21则去掉首位的00
//                    System.arraycopy(r, startPos, result, 0, 0x20);
//                    // log.trace("ecc signature r: " + MyTools.toHexStringWithBlankInterval(r));
//                    bIS.readBerTag(); // tag 02 ==> S
//                    s = bIS.readOctetString(true);
//                    startPos = (s.length == 0x21) ? 1 : 0; // 若r长度为 0x21则去掉首位的00
//                    System.arraycopy(s, startPos, result, 0x20, 0x20);
//                    // log.trace("ecc signature s: " + MyTools.toHexStringWithBlankInterval(s));
//                    // log.debug("ecc signature compact: " + MyTools.toHexStringWithBlankInterval(result));
//                }
//            } catch (Exception e) {
//                log.error("ecc signature compact Error! " + e.getMessage(), e);
//            } finally {
//                ASN1Utils.closeInputStream(isMap);
//            }
//        }

        return result;
    }

    /**
     * ecc签名格式为 30 + LEN1 + 02 + LEN2 + 00 (optional) + r + 02 + LEN3 + 00(optional) + s
     * 当r或s的第1字节大于0x80时，需要在r或s前加1字节0x00。
     * LEN3为，0x00(optional) + s 的字节长度。
     * LEN2为，0x00(optional) + r 的字节长度。
     * LEN1为，LEN2+LEN3+4字节长度。
     *
     * @param signature 压缩格式的签名
     * @return
     */
    public static byte[] uncompactSignature(byte[] signature) {
        int len = signature.length / 2;
        boolean rOptional = (signature[0] & 0x80) == 0x80; // 最高位为1 即0x80 byte值为负数
        boolean sOptional = (signature[len] & 0x80) == 0x80;
        int signatureSize = 6 + signature.length + (rOptional ? 1 : 0) + (sOptional ? 1 : 0);
        byte[] result = new byte[signatureSize];
        int pos = 0;
        result[pos++] = 0x30;
        result[pos++] = (byte) (signatureSize - 2); // 后续内容长度
        result[pos++] = 0x02;   // r tag 0x
        result[pos++] = (byte) (len + (rOptional ? 1 : 0));  // r len
        if (rOptional) result[pos++] = 0x00;  // r optional
        System.arraycopy(signature, 0, result, pos, len);
        pos += len;
        result[pos++] = 0x02;   // s tag 0x
        result[pos++] = (byte) (len + (sOptional ? 1 : 0));  // s len
        if (sOptional) result[pos++] = 0x00;  // s optional
        System.arraycopy(signature, len, result, pos, len);
        pos += len;
        return result;
    }


//    /**
//     * @param publicKey
//     * @param signatureStr
//     * @param message
//     * @return
//     * @throws Exception
//     */
//    public static boolean verify(PublicKey publicKey, String signatureStr, String message) throws Exception {
//        byte[] data = message.getBytes("UTF-8");
//        byte[] sign = Base64Utils.decodeFromString(signatureStr);
//        return verify(publicKey, sign, data, SHA256withECDSA, false); // 使用jdk签名，验签的签名值为非压缩格式
//    }
//
//    public static boolean verify(String pubKey, String signatureStr, String message, String dsaAlgorithm) throws Exception {
//        PublicKey publicKey = getPubKey(pubKey);
//        byte[] data = message.getBytes("UTF-8");
//        byte[] sign = Base64Utils.decodeFromString(signatureStr);
//        return verify(publicKey, sign, data, dsaAlgorithm, false);  // 使用jdk签名，验签的签名值为非压缩格式
//    }
//
//    public static boolean verify(PublicKey publicKey, byte[] signature, byte[] data, boolean isSignatureCompact) {
//        return verify(publicKey, signature, data, SHA256withECDSA, isSignatureCompact);
//    }


    /**
     * EC 公钥验签
     *
     * @param publicKey     公钥
     * @param data          签名的报文内容
     * @param sign          签名
     * @param signAlgorithm 签名算法: SHA256withECDSA
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static boolean verify(PublicKey publicKey, byte[] data, byte[] sign, String signAlgorithm, boolean isSignatureCompact) {
        if (isSignatureCompact) {
            sign = uncompactSignature(sign);
        }
        try {
            Signature signature = Signature.getInstance(signAlgorithm, PROVIDER);
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(sign);
        } catch (SignatureException e) {
            return false;
        } catch (NoSuchAlgorithmException e) {
            log.error("verify error! NoSuchAlgorithm! ", e);
            return false;
        } catch (InvalidKeyException e) {
            log.error("verify error! InvalidKey! ", e);
            return false;
        } catch (NoSuchProviderException e) {
            log.error("verify error! NoSuchProviderException ! ", e);
            return false;
        }
    }

    /**
     * 提取公钥
     *
     * @param pubKey base64格式的公钥
     * @return
     * @throws Exception
     */
    public static PublicKey getPubKey(String pubKey) throws Exception {
        byte[] keyBytes = Base64Utils.decodeFromString(pubKey);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        return publicKey;
    }

    /**
     * 提取私钥
     *
     * @param privKey base64格式的私钥
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivKey(String privKey) throws Exception {
        byte[] keyBytes = Base64Utils.decodeFromString(privKey);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, PROVIDER);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        return priKey;
    }

    /**
     * 加密
     *
     * @param plainText 明文
     * @param publicKey base64格式的公钥
     * @return
     * @throws Exception
     */
    public static byte[] encrypt(String plainText, String publicKey) throws Exception {
        byte[] data = plainText.getBytes(StandardCharsets.UTF_8.name());
        PublicKey pubKey = getPubKey(publicKey);
        Cipher cipher = Cipher.getInstance("ECIES", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return cipher.doFinal(data);
    }

    /**
     * 解密
     *
     * @param code       base64格式的密文
     * @param privateKey base64格式的私钥
     * @return
     * @throws Exception
     */
    public static byte[] decrypt(String code, String privateKey) throws Exception {
        byte[] data = Base64Utils.decodeFromString(code);
        PrivateKey priKey = getPrivKey(privateKey);
        Cipher cipher = Cipher.getInstance("ECIES", PROVIDER);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return cipher.doFinal(data);
    }

    /**
     * 读取pem文件（私钥），获取秘钥对
     *
     * @param pemFile
     * @return
     * @throws Exception
     */
    public static KeyPair readKeyPair(String pemFile) throws Exception {
        InputStream res = new FileInputStream(pemFile);
        Reader fRd = new BufferedReader(new InputStreamReader(res));
        Security.addProvider(new BouncyCastleProvider());
        PEMParser pemRd = new PEMParser(fRd);
        PEMKeyPair pemPair;
        KeyPair pair;
        ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier) pemRd.readObject();
        X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID);
        if (ecSpec == null) {
            throw new Exception("ecSpec not found for named curve");
        }
        pemPair = (PEMKeyPair) pemRd.readObject();
        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
        return pair;
    }

    public static void main(String[] args) throws Exception {
//        PublicKey publicKey = readCert("D:\\my\\ctl\\tsm\\RSP相关证书\\euicc\\CERT_EUICC_ECDSA_NIST.der");
//        BASE64Encoder base64Encoder = new BASE64Encoder();
//        String publicKeyString = base64Encoder.encode(publicKey.getEncoded());
//        System.out.println(publicKey.toString());
//        System.out.println("-----------------公钥--------------------");
//        System.out.println(publicKeyString);
//        System.out.println("-----------------公钥--------------------");


//        KeyPair keyPair = readKeyPair("D:\\my\\workspace\\my-backend\\eSIM\\rsp-certs\\smdp\\SK_S_SM_DP2auth_ECDSA_NIST.pem");
//        KeyPair keyPair = initKeyPair(OID.FRP256v1.getCode(), 256);

//        KeyPair keyPair = initKeyPair(OID.brainpoolP256r1.getCode(), 256);
//        AsymmetricCipherKeyPair keyPair = initKeyPair(OID.secp256r1.getCode(), 256);


        // generate ECC key
//        SecureRandom random = new SecureRandom();
//        X9ECParameters ecP = ECUtil.getNamedCurveByOid(SECObjectIdentifiers.secp256r1);
//        ECCurve ecCurve = ecP.getCurve();
//        EllipticCurve ellipticCurve = EC5Util.convertCurve(ecCurve, ecP.getSeed());
//
//        ECParameterSpec ecSpec = new ECNamedCurveSpec(
//                ECUtil.getCurveName(SECObjectIdentifiers.secp256r1),
//                ellipticCurve,
//                new ECPoint(
//                        ecP.getG().getAffineXCoord().toBigInteger(),
//                        ecP.getG().getAffineYCoord().toBigInteger()),
//                ecP.getN(),
//                ecP.getH());
//
//
//        KeyPairGenerator g = KeyPairGenerator.getInstance("EC");
//        g.initialize(ecSpec, random);
//        KeyPair keyPair = g.generateKeyPair();

    }


}
